;; =========
;; UTILITIES
;; =========

(define even-simpler-display
  (lambda (sth)
    (display
     (simple-format
      #f "~a\n" sth))))

;; ==========================
;; NON-CONTINUABLE EXCEPTIONS
;; ==========================

(define erroroneous-proc-division
  (lambda (dividend divisor)
    ;; Division by zero will raise an uncontinuable exception. "uncontinuable"
    ;; means, that at here, at this very point, the execution cannot continue,
    ;; meaning, that erroroneous-proc-division does not know, what to do next,
    ;; when it receives a divisor equal to zero.
    (/ dividend divisor)))

;; Use an exception handler to handle the exception thrown when dividing by
;; zero. `with-exception-handler` takes 2 arguments. The installed exception
;; handler and the code to run wrapped in a lambda to avoid immediate
;; evaluation, called a "thunk".
(with-exception-handler
    ;; The exception handler takes 1 argument, which is whatever is given to
    ;; `raise` or `raise-continuable` when the exception is raised. In the case
    ;; of dividing by zero, we get a `#<r6rs:record:&compound-condition>`, which
    ;; we can inspect.
    (lambda (condition-or-value)
      (display
       (simple-format
        #f
        (string-append "====\n"
                       "condition or value:\n"
                       "~a\n"
                       "====\n")
        condition-or-value)))
  ;; The thunk is always a procedure taking 0 arguments. Its invocation is
  ;; running the code, which could cause an exception.
  (lambda ()
    ;; Divide by zero to cause an exception. Dividing by zero will result in a
    ;; non-continuable exception. The exception will be dealt with and the
    ;; program will then exit with an error.(simple-conditions condition)
    (erroroneous-proc-division 1 0))
  ;; From the Guile reference manual:

  ;; "[...] it’s often the case that one would like to handle an exception by
  ;; unwinding the computation to an earlier state and running the error handler
  ;; there. After all, unless the raise-exception call is continuable, the
  ;; exception handler needs to abort the continuation. To support this use
  ;; case, if with-exception-handler was invoked with #:unwind? #t is true,
  ;; raise-exception will first unwind the stack by invoking an escape
  ;; continuation (see call/ec), and then invoke the handler with the
  ;; continuation of the with-exception-handler call." --
  ;; https://www.gnu.org/software/guile/docs/master/guile.html/Raising-and-Handling-Exceptions.html

  ;; Here the exception is non-continuable, so according to the reference
  ;; manual, we need to call with-exception-handler with #:unwind? being set to
  ;; #t for normal exception handling. If we do not do this, a (different?)
  ;; non-continuable exception is raised.

  ;; Q: Why unwind the stack?

  ;; A:

  ;; If the execution cannot be continued (non-continuable exception) at the
  ;; point, where the exception is raised, it means, that in that context, there
  ;; was insufficient information to continue the execution in useful way.

  ;; Unwinding the stack means discarding stack frames of procedure calls. This
  ;; happens up to the point, where an exception handler is defined. The result
  ;; of unwinding the stack is, that we only leave intact the environment, which
  ;; was available when the exception handler was defined.

  ;; We have the knowledge at this point, that an exception occurred. That is
  ;; more than we knew, when we called the procedure, which raised the
  ;; exception. Furthermore the exception can contain information about the kind
  ;; of thing that went wrong. This information hand-over facility should be
  ;; used to give sufficient information to the exception handler, so that the
  ;; handler can continue execution in a useful way. The exception enables us to
  ;; store important information from the environment at the point where the
  ;; exception was raised. Then we have no need to keep the environment of the
  ;; point, where the exception was raised and can unwind the stack.
  #:unwind? #t)

;; Lets see what happens, if we do not set #:unwind? to #t. (Note: This example
;; should result in an exception, even though we are using
;; with-exception-handler, because we are doing it the wrong way.)

(with-exception-handler
    (lambda (condition-or-value)
      (display
       (simple-format
        #f
        (string-append "====\n"
                       "condition or value:\n"
                       "~a\n"
                       "====\n")
        condition-or-value)))
  (lambda ()
    (erroroneous-proc-division 1 0))
  ;; #:unwind? #f is the default.
  #:unwind? #f)

;; As described above, a non-continuable exception is raised and the program
;; breaks, which is usually not what one wants in many cases.

;; ======================
;; CONTINUABLE EXCEPTIONS
;; ======================

;; Here is another procedure, which raises a continuable exception. When it is
;; dealt with, the program will continue and not exit with the error.
(define erroroneous-proc-2
  (lambda ()
    (raise-continuable "I am an error.")))

(with-exception-handler
    (lambda (condition-or-value)
      (display (simple-format #f "~a\n" condition-or-value)))
  (lambda ()
    (erroroneous-proc-2)))
